/*****************************************************************************
 *  CommLayerLib for Android - BluetoothLayer.java
 *  Copyright (C) 2012  Matteo Piscitelli
 *  E-mail: matteo@picciux.it
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 *  For further info, check http://code.google.com/p/java-comm-layer/
 *
 *  SVN: $Id: BluetoothLayer.java 45 2012-10-11 16:48:14Z picciux@gmail.com $
 *  
 *****************************************************************************/

package it.picciux.commlayer.android.bluetooth;

import java.io.IOException;
import java.util.UUID;
import java.util.Vector;
import java.util.logging.Logger;

import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothClass;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import it.picciux.commlayer.CommLayerException;
import it.picciux.commlayer.CommLayerPort;
import it.picciux.commlayer.DataWriter;
import it.picciux.castle.linklive.android.*;

public class BluetoothLayer extends
		it.picciux.commlayer.bluetooth.BluetoothLayer {
	private class BTBroadcastReceiver extends BroadcastReceiver {

		@Override
		public void onReceive(Context context, Intent intent) {
	        String action = intent.getAction();
	        
	        // When discovery finds a device
	        if (BluetoothDevice.ACTION_FOUND.equals(action)) {
	        	log.finer("BT device discovered");
	        	
	            // Get the BluetoothDevice object from the Intent
	            BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);

	            // check device class
	            if (! checkBTDeviceClass(device.getBluetoothClass())) {
	            	log.finer("Found device class doesn't interest us: discard");
	            	return;
	            }
	            
	            String friendlyName = intent.getStringExtra(BluetoothDevice.EXTRA_NAME);
	            
	            // if we have friendly name, immediately add the device. Otherwise
	            // wait for it to be returned by ACTION_NAME_CHANGED
	            log.finer("Found device address: '" + device.getAddress() + "'");
	            if (friendlyName != null) {
	            	log.finer("We have device name: '" + friendlyName + "'. Add and notify it");
    	            CommLayerPort p;
            		p = new CommLayerPort(device.getAddress(), friendlyName);
            		scanList.add(p);
	            
		            //notify listeners the new port
		            notifyListeners(PORTSCAN_PORT_FOUND, p);
            	} else {
            		log.finer("We don't have device friendly name: wait for it to come...");
            	}
	        
	        // When discovery retrieves a device name (if not returned by ACTION_FOUND
	        } else if (BluetoothDevice.ACTION_NAME_CHANGED.equals(action)) {
	            BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
	            String friendlyName = intent.getStringExtra(BluetoothDevice.EXTRA_NAME);
	            
	            log.finer("Received name '" + friendlyName + "' for device '" + device.getAddress() + "'. Add and notify");
	            
	            CommLayerPort p;
        		p = new CommLayerPort(device.getAddress(), friendlyName);
        		scanList.add(p);
            
	            //notify hts the new port
	            notifyListeners(PORTSCAN_PORT_FOUND, p);
	            
	        // When discovery starts
	        } else if (BluetoothAdapter.ACTION_DISCOVERY_STARTED.equals(action)) {
	    		notifyListeners(PORTSCAN_STARTED);
	    		
	    	// When discovery finishes
	        } else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(action)) {
	        	log.finer("Scan terminated: found " + scanList.size() + " device(s). Notifying them");
	        	
	        	CommLayerPort[] portList = new CommLayerPort[scanList.size()];
	        	
	        	scanList.copyInto(portList);
	        	/*for (int i = 0; i < scanList.size(); i++)
	        		portList[i] = scanList.get(i);*/
	        	
	        	scanList.clear();
	        	scanList = null;
	        	context.unregisterReceiver(bReceiver);
	        	bReceiver = null;
	        	if (canceled)
	        		notifyListeners(PORTSCAN_CANCELED, portList);
	        	else
	        		notifyListeners(PORTSCAN_COMPLETED, portList);
	        }
		}
		
	}
	
	private BluetoothAdapter adapter;
	private BluetoothDevice device = null;
	private BluetoothSocket sock = null;
	private BroadcastReceiver bReceiver = null;	
	private Context context;
	
	private Vector<CommLayerPort> scanList;
	
	private boolean canceled = false;
	
	private static final UUID sppUUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");

	private static Logger log = Logger.getLogger("it.picciux.commlayer.android.bluetooth");
	
	public BluetoothLayer(Context context) throws CommLayerException {
		this.context = context;
		this.adapter = BluetoothAdapter.getDefaultAdapter();
		if (this.adapter == null) {
			throw new CommLayerException(context.getText(R.string.bt_notavailable).toString(), BLUETOOTH_NOT_AVAIL);
		} else if (!this.adapter.isEnabled()) {
			throw new CommLayerException(context.getText(R.string.bt_disabled).toString(), BLUETOOTH_DISABLED);
		}
	}

	private boolean checkBTDeviceClass(BluetoothClass c) {
		int sc = getSettings().getServiceClass();
		int majdc = getSettings().getMajorDeviceClass();
		int mindc = getSettings().getMinorDeviceClass();
		
		if (sc == 0 && majdc == 0 && mindc == 0) return true;
		
        return (
        		c.hasService(sc) && 
        		c.getDeviceClass() == (majdc | mindc)
        );	
	}
	
	

	@Override
	public boolean scan() {
		if (adapter == null) return false; 
		
		canceled = false;
		scanList = new Vector<CommLayerPort>();
		
		// Register the BroadcastReceiver
		IntentFilter filter = new IntentFilter();
		filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_STARTED);
		filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
		filter.addAction(BluetoothDevice.ACTION_FOUND);
		filter.addAction(BluetoothDevice.ACTION_NAME_CHANGED);
		bReceiver = new BTBroadcastReceiver();		
		context.registerReceiver(bReceiver, filter); // Will be unregistered when discovery finishes
		
		if (!adapter.startDiscovery()) {
			notifyListeners(PORTSCAN_ERROR);
			return false;
		}
		return true;
	}

	@Override
	public void cancelScan() {
		canceled = true;
		adapter.cancelDiscovery(); 
	}

	@Override
	protected void tryConnection() throws CommLayerException {
		if (adapter == null) 
			throw new CommLayerException(context.getText(R.string.bt_notavailable).toString(), BLUETOOTH_NOT_AVAIL);
		else if (! adapter.isEnabled()) 
			throw new CommLayerException(context.getText(R.string.bt_disabled).toString(), BLUETOOTH_DISABLED);
	
		if (getSettings().getUrl().length() == 0) 
			throw new CommLayerException("No config available!", NOCONFIG);
		
		try {
			log.fine("Connecting to " + CommLayerPort.getName(getSettings().getUrl()));
			device = adapter.getRemoteDevice(CommLayerPort.getName(getSettings().getUrl()));
			log.fine("device bond state is: " + device.getBondState());
		} catch (IllegalArgumentException e) {
			throw new CommLayerException(e, CONNECTION_ERROR);
		}
		
		// connection-thread 
		try {
			sock = device.createRfcommSocketToServiceRecord(sppUUID);
			sock.connect(); //will block until connected or exception
			in = sock.getInputStream();
			out = sock.getOutputStream();
		} catch (IOException e) {
			try {
				sock.close();
				in.close();
				out.close();
			} catch (IOException trash) {
			}

			throw new CommLayerException(e.getMessage(), CONNECTION_FAILED);
		}
		
		if (notifyOnDataAvailable) {
			dataReader = new DataReader() {
				@Override
				public void setThreadData() {
					setName("Bluetooth DataReader thread");
				}
			};
		}

		if (writeOnSeparateThread)
			dataWriter = new DataWriter() {
				@Override
				protected void setThreadData() {
					setName("Bluetooth DataWriter thread");
				}
			};
		

	}

	@Override
	protected void doDisconnect() {
		try {
			if (sock != null) sock.close();
		} catch (IOException e ) {
		}

		try {
			if (out != null) out.close();
		} catch (IOException e ) {
		}
			
		try {			
			if (in != null) in.close();
		} catch (IOException e ) {
		}
		
		in = null;
		out = null;
		sock = null;
	}

	@Override
	protected void setConnectThread(Thread thread) {
		thread.setName("Bluetooth connection thread");
	}

	@Override
	public Settings getSettings() {
		return (Settings) super.getSettings();
	}

	
	
	
}
